Repository navigation
Conversation
Rebased onto `52671355` (the optic-class answer: `Z = Xo`, `Function1BroadcastOptic`, `RepresentativeIndex`, the positional write). That commit and this one answer different questions, and this rebase keeps upstream's machinery — and upstream's *idiom* — wherever it is better: - **kept** `Function1BroadcastOptic` as the kernel's per-index write hook (one value per position, no bundle read it did not build) and the bridge's `RepresentativeIndex[X0]` witness; - **added** the sum: the focus half is `F[A] | Broadcast[F, A]` (both cases `private[eo]`), with `MultiFocus.broadcast` / `.broadcast` as the public handles and `.foci` total for both (the index-free case carries its `F[A]` image); - **added** the kernel's remaining cases: a composite inner is written per position with the leftover *its own* read produced (upstream forges it, so a `fromLensF` inner NPEs), and a tabulating ∘ tabulating composite writes the exact inverse of its diagonal read. That last one needs `C =:= D`, which is a property of the *optic* — so it rides on the optic as `Optic.SameFocus` (mixed into every `andThen` composite from the call site's `summonFrom`, and into the monomorphic factories `representable` / `tuple` / `apply` with `Some(…)`), and the kernel reads it off the `inner` it is already handed. `AssociativeFunctor` and the composition algebra are untouched (zero diff vs upstream). - **deleted `unobserved`** — no write needs a stand-in any more. Its three historical sites are fixed at the source: the kernel records the inner leftovers its read observed, the bridge takes a real index witness, and the shape-collapsing `collectList` now hands the write the leftover its own read produced (`o.from((bundle.context, List(agg(bundle.foci))))`), which fixes a live NPE / `MatchError` on Lens / Prism / Optional provenances (probed before the fix). New coverage: `tests/.../MultiFocusCrossFamilySpec.scala` is the runtime twin of `CompositionMatrixSpec` — nine property blocks over every inbound provenance (generic factory, polymorphic factory, Iso/Lens/Prism/Optional bridges, a composite of two shipped optics) through `modify` / `replace` / `collectMap` / `collectWith` / `collectList` / `foldMap` / `headOption` / `length`, plus both composition directions across Iso / Lens / Prism / Optional with independently computed expectations. The collapsing aggregate is asserted only where the optic's leftover is count-agnostic; a composite's is the one named boundary (see the note). Measured against `52671355` (throwaway printing spec, removed again): `tuple ∘ tuple.modify(identity)` on `((1,2),(3,4))` → `((1,4),(1,4))` there, identity here; `replace(9)` → `((9,9),(9,9))` there, `((9,2),(3,9))` here; `tuple ∘ (shim ∘ shim).modify(_+1)` on `(1,2)` → `(2,2)` there, `(2,3)` here; a `fromLensF` inner NPEs there; `collectList` NPEs / MatchErrors there and works here. Upstream's own `MultiFocusFunction1Spec` is kept verbatim (14 examples / 349 expectations) and passes against this kernel; this branch's core `MultiFocusFunction1CompositionSpec` adds 13 blocks / 1003 expectations and `external/EoOpaqueSurfaceSpec` pins the public surface from outside the `eo` package tree. Gates (sbt 1.13.0, JDK 25): root `test` 237 examples / 0 failures, `scalafmtCheckAll`, `scalafixAll --check`, `scalafmtSbtCheck`, `benchmarks/scalafmtCheck`, `mimaReportBinaryIssues`, `githubWorkflowCheck`, `docs/mdoc` 0 errors, `docs/laikaSite`, `core/doc`.
… stryker blocker (#115) (#128) * build(deps): bump actions/cache from v4 to v6 Supersedes #118. Drop-in verified: action.yml inputs are byte-identical between v4.3.0 and v6.1.0 (only the node20 -> node24 runtime line differs); all 18 steps pass exactly {path, key, restore-keys}; @v6 resolves to v6.1.0, which adds graceful handling of read-only cache tokens (the accurate 'cache write denied' warning on fork PRs instead of the bogus 'another job may be creating this cache'). * build(deps): bump droste-core from 0.9.0-M3 to 0.10.0 Supersedes #124. Benchmark-only dependency (never published downstream). 0.10.0 is dependency maintenance upstream (cats update, scala-collection-compat 2.13.0, Scala.js 1.18.2); our droste surface (Fix, scheme.{cata,ana,hylo}, Algebra/Coalgebra) compiles unchanged and all nine SchemesBench benchmarks execute on the new version. * build(deps): bump sbt-stryker4s from 0.20.4 to 1.1.1 Supersedes #126. No removed or renamed features reach this build: the setting keys used in build.sbt (strykerReporters, strykerExcludedMutations, strykerThresholdsBreak) are unchanged, the borrowed-tests wiring still resolves, and 1.x requires sbt >= 1.11.2 (we run 1.13.0). Verified with a schemes smoke run (48/58 killed, 0 NoCoverage) and a full avroIntegration run (481 killed / 51 survived / 58 known-macro NoCoverage, no crash). Decisive for issue #115: 1.1.1 fixes stryker4s' rollback invariant crash ('cases should be non-empty') that killed avro's mutation run on main — 0.20.4's mutant removal could empty a Term.Match by deleting its default Pat.Wildcard case; 1.1.1 filters it. Also refreshes the invocation-doctrine comment: the module-scoped <m>/stryker form works again since 0.20.4. * fix(avro): keep AvroWalk's null-narrowing outside stryker's reach Addresses #115 blocker 1. The flow-typed 'val here = if index != null then index else ...' loses its narrowing the moment stryker4s' mutator rewrites the condition, so two mutants died as compile errors instead of being exercised (and on 0.20.4 that rollback crashed the whole run). Express the narrowing as a match on the null sentinel with an explicit JMap[String, Integer] type: a match has no condition to mutate, so the narrowing is structural rather than flow-based. (The ascribe + 'index.nn' spelling from the issue does not compile here — the guard already narrows, E216 fires, and -Werror rejects the warning.) Applied at both occurrences (totalNominalIndex and recordSlots); AvroCompileError mutants from AvroWalk go to zero. * docs: refresh version pins and the stryker invocation doctrine CLAUDE.md said Scala 3.8.3 / sbt 1.12.9; the project builds Scala 3.9.0 (build.sbt scala3Version) on sbt 1.13.0 (project/build.properties). CONTRIBUTING.md's bootstrap list pointed Scala at build.properties and omitted the JDK 25 requirement for kyo + the docs site. The 'invoke as project <m>; stryker, NOT <m>/stryker' claim stopped being true in 0.20.4 (module-scoped task resolution was fixed upstream); state the history instead of forbidding the working form. The mutationAll alias also covers zio and kyo now, not just the original eight modules. quality-assurance.md's scoverage path follows the Scala version. * build(ci): bump the workflow generator's actions/cache ref to v6 The cache bump landed as a hand-edit of the generated ci.yml (mirroring Dependabot's #118 diff), which githubWorkflowCheck rightly flags: ci.yml is generator-owned (CONTRIBUTING.md: never hand-edit it). This pairs the edit with its source of truth — UseRef.Public("actions", "cache", "v6") in the setup-java cache patch. githubWorkflowGenerate now emits zero workflow drift, so the committed ci.yml is exactly generated output, and githubWorkflowCheck passes.
|
🚀 Cloudflare Pages preview for https://364caec8.cats-eo-docs.pages.dev Branch alias: https://spike-grate-broadcast-sum.cats-eo-docs.pages.dev Built from commit |
Benchmark A/BAllocation (B/op) — authoritative
442 more benchmarks
Timing (ns/op) — directional only, same-VM but shared runner
base_sha: |
…over; supersede the grate-constancy spike - collectList: replace the unobserved[o.X] stand-in with the bundle's real context, so composite/Prism/Optional provenances rebuild correctly (empty composite -> Nil, prepend-zero Prism miss -> unchanged) instead of NPE/MatchError. Contract documented: singleton FOCUS VECTOR, not necessarily singleton source. - MultiFocusCrossFamilySpec: runtime twin of CompositionMatrixSpec across every inbound provenance + collectList boundary tests. - docs/research: keep the #129 spike record with a SUPERSEDED banner; add 2026-10-05-multifocus-redesign.md consolidating the review into the decision to rebuild the grate side on a product-indexed (full-grid) IndexedGlass. The grate-sum machinery itself is withdrawn from this PR.
Superseded: the grate-constancy design is withdrawnRound-2 review probes (see Decision: full-grid. The grate side will be re-implemented around a product-indexed split/rebuild optic ( What this branch now carriesThe variable-shape (non-grate) fixes from the spike, rebased onto current main — independent of the grate redesign and safe to land:
The withdrawn grate machinery leaves no residue: no Gatessbt 1.13.0 (local JDK 27; CI lanes cover 17/21/25): root |
…tic (full-grid composition) Standalone spike for the grate re-design (docs/research/2026-10-05-multifocus-redesign.md SS5 phase 1). Composition yields index (I, J) and context (Ctx, I => inner.Ctx): no C =:= D evidence, no index-== addressing, no SameFocus-style witness. Grate alias = Context = Unit case (not yet composition-closed — normalization is an open item). GlassSpec: 10 examples / 505 expectations covering arbitrary tabulations, 3-level associativity, type-changing composition through a non-inline generic helper, permuted/empty/Unit/NaN/signed-zero indexes, and the explicit full-grid vs diagonal semantic change.
Separate fixed-index optics from container MultiFocus
This PR replaces the original grate-constancy experiment with the full-grid design and removes the dedicated legacy Grate implementation from MultiFocus. Container traversal and aggregation remain intact.
Fixed-index optics
IndexedextendsOpticwith a single named-tuple carrier:The existing existential
Xis the residual context.toandfromare the only decomposition/reconstruction methods: no Context alias, opaque GlassK, pack/accessor boilerplate, split/rebuild synonyms, SameFocus, CoordinateEq, or representative-index witness.The dedicated glass
andThenretains both index axes(I,J)and per-coordinate contexts. Reads expose the full grid; polymorphic writes rebuild each row from its corresponding slice, without coordinate equality or evidence visibility influencing behavior.Indexed.Grateis theX = Unitalias; context-bearing optics are the more general Glass case.Removed legacy APIs (source/binary breaking)
representable,tuple,zip,zipWithGeneric MultiFocus apply/pApply, functor-based modification/widening and representable-gated at remain; function containers can still be modified pointwise through these generic operations. List/PSVec and other container kernels are unchanged. Traversal.two/three/four already use PSVec and retain their tested composition paths.
Correctness and migration
Remaining prototype limits
No general classical-family Composer bridges or same-index AssociativeFunctor are installed. The existing writable-outer Optic extension supports glass.andThen(Modify). Unit-context composition normalization and context-free zip/collect operations remain open; no top-level Grate companion exists yet.
Verification
Local sbt 1.13.0 / Scala 3.9.0 / JDK 27:
Python is unavailable locally, so QA fixture counts were independently checked with the generator-equivalent parser; the full Python QA generator was not executed here. GitHub checks on this new head are pending.
Runnable usage examples
site/docs/indexed.mddocuments Grates and context-bearing Glasses with 20 executable assertions:fromreconstruction;The trait and companion now live in
optics/Indexed.scala; only theGlassFcarrier alias remains indata/Glass.scala. Navigation, reference pages, imports, QA generation and agent documentation use these names consistently.Latest checks: focused core/law/shape tests and benchmarks compilation passed; scalafmtCheckAll and scalafixAll --check passed; docs/mdoc executes all new examples successfully; docs/laikaSite and core/doc passed. Existing mdoc/Laika anchor warnings and two pre-existing Traversal Scaladoc links remain. GitHub CI on the new head is pending.